package view.mt4j.component.note;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.mt4j.MTApplication;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.shapes.MTLine;
import org.mt4j.components.visibleComponents.shapes.MTPolygon;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.input.gestureAction.TapAndHoldVisualizer;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.MTGestureEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleEvent;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldEvent;
import org.mt4j.input.inputProcessors.componentProcessors.tapAndHoldProcessor.TapAndHoldProcessor;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;

import command.project.component.MoveComponentCommand;
import command.project.component.ResizeComponentCommand;
import controller.ApplicationController;
import controller.view.StatechartController;

import app.ViewConstants;

import view.interfaces.component.note.NoteView;
import view.mt4j.component.ghost.MTRectangleGhost;
import view.mt4j.util.ProcessorHelper;

import model.StatechartComponent;
import model.uml.note.Note;
import model.util.Dimension;
import model.util.Point;

public class NoteViewImpl extends MTPolygon implements PropertyChangeListener,
		NoteView {

	private final Note note;

	private ApplicationController applicationController;
	private StatechartController statechartController;

	private static final float CORNER_SIZE = 10f;
	private static final float RIGHT_TEXT_PADDING = 5f;

	private MTApplication app;
	private MTTextArea mtTextArea;
	private MTLine horizontalLine = null;
	private MTLine verticalLine = null;

	private Vector3D firstCursorStart;
	private Vector3D secondCursorStart;
	private Vector3D tv;
	private MTRectangle ghost;

	// FIXME drawText and drawShape might use wrong TRANSFORMSPACE or values
	public NoteViewImpl(MTApplication app, Note note) {
		super(app, new Vertex[] {});
		this.setName("NoteViewImpl : Note (id=" + note.getId() + ")");
		this.app = app;
		this.note = note;
		note.addPropertyChangeListener(Note.Property.DIMENSION, this);
		note.addPropertyChangeListener(Note.Property.TEXT, this);
		note.addPropertyChangeListener(StatechartComponent.Property.LOCATION,
				this);
		drawText();
		// adaptNoteToView() is needed here to test too small sizes
		adaptNoteToView();
		drawShape();
		handleNoteViewProcessor();
	}

	@Override
	public Dimension getSize() {
		return note.getSize();
	}

	public void setController(ApplicationController applicationController,
			StatechartController statechartController) {
		this.applicationController = applicationController;
		this.statechartController = statechartController;
	}

	@Override
	public Point getLocation() {
		return note.getLocation();
	}

	@Override
	public void setLocation(Point point) {
		if (applicationController != null && statechartController != null) {
			MoveComponentCommand moveComponentCommand = new MoveComponentCommand(
					applicationController.getModel().getCurrentProject()
							.getId(), note, point);
			statechartController.executeCommand(moveComponentCommand);
		}
	}

	@Override
	public void setSize(Dimension dimension) {
		if (applicationController != null && statechartController != null) {
			ResizeComponentCommand resizeComponentCommand = new ResizeComponentCommand(
					applicationController.getModel().getCurrentProject()
							.getId(), note, dimension);
			statechartController.executeCommand(resizeComponentCommand);
		}
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(Note.Property.DIMENSION.name())) {
			System.out.print("dimension changed ! ");
			drawText();
			// causes loop when multi devices
			// adaptNoteToView();
			drawShape();
		} else if (evt.getPropertyName().equals(Note.Property.TEXT.name())) {
			System.out.print("text changed ! ");
			drawText();
			adaptNoteToView();
			drawShape();
		} else if (evt.getPropertyName().equals(
				StatechartComponent.Property.LOCATION.name())) {
			System.out.print("location changed ! ");
			drawText();
			adaptNoteToView();
			drawShape();
		}
	}

	private void handleNoteViewProcessor() {
		this.removeAllGestureEventListeners();

		// resizing note, not scaling it !
		this.addGestureListener(ScaleProcessor.class,
				new IGestureEventListener() {

					@Override
					public boolean processGestureEvent(
							MTGestureEvent gestureEvent) {
						ScaleEvent scaleEvent = (ScaleEvent) gestureEvent;
						switch (scaleEvent.getId()) {

						case MTGestureEvent.GESTURE_STARTED: {
							System.out.println("SCALING ...");
							firstCursorStart = scaleEvent.getFirstCursor()
									.getPosition();
							secondCursorStart = scaleEvent.getSecondCursor()
									.getPosition();
							break;
						}
						case MTGestureEvent.GESTURE_UPDATED: {
							if (ghost == null) {
								Point ghostPosition = new Point(note
										.getLocation().x
										- note.getSize().width
										/ 2, note.getLocation().y
										- note.getSize().height / 2);
								ghost = new MTRectangleGhost(app,
										ghostPosition, note.getSize(),
										MTColor.BLUE);
								addChild(ghost);
							}
							Vector3D firstCursorEnd = scaleEvent
									.getFirstCursor().getPosition();
							Vector3D secondCursorEnd = scaleEvent
									.getSecondCursor().getPosition();
							try {
								float sx = (secondCursorEnd.x - firstCursorEnd.x)
										/ (secondCursorStart.x - firstCursorStart.x);
								float sy = (secondCursorEnd.y - firstCursorEnd.y)
										/ (secondCursorStart.y - firstCursorStart.y);
								if (sx > 0 && sy > 0) {
									ghost.setSizeXYGlobal(note.getSize().width
											* sx, note.getSize().height * sy);
									ghost.setPositionGlobal(new Vector3D(
											Math.abs((firstCursorEnd.x - secondCursorEnd.x) / 2),
											Math.abs((firstCursorEnd.y - secondCursorEnd.y) / 2)));
								}
							} catch (Exception ex) {
								// do nothing, just because dividing
							}
							break;
						}

						case MTGestureEvent.GESTURE_ENDED: {
							System.out.println("SCALING ENDED");
							Vector3D firstCursorEnd = scaleEvent
									.getFirstCursor().getPosition();
							Vector3D secondCursorEnd = scaleEvent
									.getSecondCursor().getPosition();
							try {
								float sx = (secondCursorEnd.x - firstCursorEnd.x)
										/ (secondCursorStart.x - firstCursorStart.x);
								float sy = (secondCursorEnd.y - firstCursorEnd.y)
										/ (secondCursorStart.y - firstCursorStart.y);
								if (sx > 0 && sy > 0) {
									// only set size when not too small ! (to
									// avoid loop)
									// see constructor
									Dimension dimension = new Dimension(note
											.getSize().width * sx, note
											.getSize().height * sy);
									Note tmpNote = new Note(null, null,
											new Point(), dimension, note
													.getText());
									NoteViewImpl tmpNoteViewImpl = new NoteViewImpl(
											app, tmpNote);
									// TODO check if it works as expected
									if (tmpNoteViewImpl.note.getSize().height
											.equals(dimension.height)) {
										setSize(dimension);
										float xCenter = Math
												.abs((firstCursorEnd.x - secondCursorEnd.x) / 2);
										float yCenter = Math
												.abs((firstCursorEnd.y - secondCursorEnd.y) / 2);
										Point newLocation = new Point(xCenter,
												yCenter);
										setLocation(newLocation);
									} else {
										System.out
												.println("Scaling canceled, NoteView is too small");
									}
								} else {
									System.out.println("Scaling canceled");
								}
							} catch (Exception e) {
								// do nothing, just because dividing
							}
							if (ghost != null) {
								ghost.removeFromParent();
								ghost = null;
							}
							break;
						}
						default:
							break;
						}
						return false;
					}
				});

		// dragging whole component
		this.addGestureListener(DragProcessor.class,
				new IGestureEventListener() {
					@Override
					public boolean processGestureEvent(
							MTGestureEvent gestureEvent) {
						DragEvent dragEvent = (DragEvent) gestureEvent;
						switch (dragEvent.getId()) {
						case MTGestureEvent.GESTURE_STARTED:
							System.out.println("DRAGGING ...");
							tv = new Vector3D();
							break;
						case MTGestureEvent.GESTURE_UPDATED:
							if (ghost == null) {
								Point ghostPosition = new Point(note
										.getLocation().x
										- note.getSize().width
										/ 2, note.getLocation().y
										- note.getSize().height / 2);
								ghost = new MTRectangleGhost(app,
										ghostPosition, note.getSize(),
										MTColor.RED);
								addChild(ghost);
							}
							tv.addLocal(dragEvent.getTranslationVect());
							ghost.translate(dragEvent.getTranslationVect());
							break;
						case MTGestureEvent.GESTURE_ENDED:
							System.out.println("DRAGGING ENDED");
							if (tv.x != 0f && tv.y != 0f) {
								setLocation(new Point(note.getLocation().x
										+ tv.x, note.getLocation().y + tv.y));
							}
							if (ghost != null) {
								ghost.removeFromParent();
								ghost = null;
							}
							break;
						default:
							break;
						}
						return false;
					}
				});

		this.addGestureListener(TapAndHoldProcessor.class,
				new TapAndHoldVisualizer(app, this));
		this.addGestureListener(TapAndHoldProcessor.class,
				new IGestureEventListener() {
					public boolean processGestureEvent(MTGestureEvent ge) {
						TapAndHoldEvent th = (TapAndHoldEvent) ge;
						switch (th.getId()) {
						case TapAndHoldEvent.GESTURE_STARTED:
							System.out.println("TAP & HOLD ...");
							break;
						case TapAndHoldEvent.GESTURE_UPDATED:
							break;
						case TapAndHoldEvent.GESTURE_ENDED:
							if (th.isHoldComplete()) {
								System.out.println("TAP & HOLD COMPLETE");
							} else {
								System.out.println("TAP & HOLD ABORTED");
							}
							break;
						default:
							break;
						}
						return false;
					}
				});
	}

	private void drawText() {
		if (mtTextArea == null) {
			mtTextArea = new MTTextArea(app, note.getLocation().x
					- note.getSize().width / 2, note.getLocation().y
					- note.getSize().height / 2, note.getSize().width
					- RIGHT_TEXT_PADDING, note.getSize().height, FontManager
					.getInstance().createFont(app, ViewConstants.FONT,
							ViewConstants.FONT_SIZE, ViewConstants.FONT_COLOR));
			mtTextArea.setText(note.getText());
			mtTextArea.setNoStroke(true);
			mtTextArea.setNoFill(true);
			addChild(mtTextArea);
			handleTextProcessors();
		} else {
			mtTextArea
					.setSizeLocal(note.getSize().width, note.getSize().height);
			mtTextArea.setPositionRelativeToParent(new Vector3D(note
					.getLocation().x, note.getLocation().y));
		}

	}

	private void handleTextProcessors() {
		mtTextArea.removeAllGestureEventListeners();
		ProcessorHelper.delegateGestureEventTo(mtTextArea.getParent(),
				DragProcessor.class, mtTextArea);
		ProcessorHelper.delegateGestureEventTo(this, ScaleProcessor.class,
				mtTextArea);
		this.mtTextArea.registerInputProcessor(new TapAndHoldProcessor(app,
				1000));
		ProcessorHelper.delegateGestureEventTo(this, TapAndHoldProcessor.class,
				mtTextArea);

	}

	private void adaptNoteToView() {
		float totalTextHeight = (mtTextArea.getFont().getFontAbsoluteHeight() + 2)
				* mtTextArea.getLineCount();
		float availableHeightForText = note.getSize().height
				- (2 * mtTextArea.getInnerPaddingTop());
		float missingTextHeight = availableHeightForText - totalTextHeight;
		if (missingTextHeight < 0) {
			System.out.println("needed Text Height = " + -missingTextHeight);
			float newShapeHeight = note.getSize().height - missingTextHeight;
			setSize(new Dimension(note.getSize().width, newShapeHeight));
			setLocation(new Point(note.getLocation().x, note.getLocation().y
					- missingTextHeight / 2));
		}
	}

	private void drawShape() {
		drawOutfit();
		drawCorner();
	}

	private void drawOutfit() {
		this.setVertices(new Vertex[] {
				new Vertex(note.getLocation().x + note.getSize().width / 2
						- CORNER_SIZE, note.getLocation().y
						- note.getSize().height / 2),
				new Vertex(note.getLocation().x - note.getSize().width / 2,
						note.getLocation().y - note.getSize().height / 2),
				new Vertex(note.getLocation().x - note.getSize().width / 2,
						note.getLocation().y + note.getSize().height / 2),
				new Vertex(note.getLocation().x + note.getSize().width / 2,
						note.getLocation().y + note.getSize().height / 2),
				new Vertex(note.getLocation().x + note.getSize().width / 2,
						note.getLocation().y - note.getSize().height / 2
								+ CORNER_SIZE),
				new Vertex(note.getLocation().x + note.getSize().width / 2
						- CORNER_SIZE, note.getLocation().y
						- note.getSize().height / 2) });
		this.setStrokeColor(ViewConstants.LINE_COLOR);
	}

	private void drawCorner() {
		if (horizontalLine != null && this.containsChild(horizontalLine)) {
			this.removeChild(horizontalLine);
		}
		horizontalLine = new MTLine(app, note.getLocation().x
				+ note.getSize().width / 2, note.getLocation().y
				- note.getSize().height / 2 + CORNER_SIZE, note.getLocation().x
				+ note.getSize().width / 2 - CORNER_SIZE, note.getLocation().y
				- note.getSize().height / 2 + CORNER_SIZE);
		horizontalLine.setStrokeColor(ViewConstants.LINE_COLOR);
		horizontalLine.removeAllGestureEventListeners();
		this.addChild(horizontalLine);

		if (verticalLine != null && this.containsChild(verticalLine)) {
			this.removeChild(verticalLine);
		}
		verticalLine = new MTLine(app, note.getLocation().x
				+ note.getSize().width / 2 - CORNER_SIZE, note.getLocation().y
				- note.getSize().height / 2, note.getLocation().x
				+ note.getSize().width / 2 - CORNER_SIZE, note.getLocation().y
				- note.getSize().height / 2 + CORNER_SIZE);
		verticalLine.setStrokeColor(ViewConstants.LINE_COLOR);
		verticalLine.removeAllGestureEventListeners();
		this.addChild(verticalLine);
	}
	
	@Override
	public void select() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void unselect() {
		// TODO Auto-generated method stub
		
	}

}
